{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Sync overview\n",
    "\n",
    "Feature layers includes a `Sync` capability, which when enabled, allows client applications to take feature layers offline, perform edits, and sync it back to the layer. When you checkout some features and store it offline in the client, you call that a `replica`. The `FeatureLayerCollection` class under the `features` module allows users to create and work with replicas. The workflow of using sync involves these three operations:\n",
    "- Create replica\n",
    "- Synchronize replica\n",
    "- Unregister replica\n",
    "\n",
    "To learn more about this feature, refer to the [Sync Overview](https://developers.arcgis.com/rest/services-reference/enterprise/sync-overview/) documentation."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "toc": true
   },
   "source": [
    "<h1>Table of Contents<span class=\"tocSkip\"></span></h1>\n",
    "<div class=\"toc\"><ul class=\"toc-item\"><li><span><a href=\"#Sync-overview\" data-toc-modified-id=\"Sync-overview-1\"><span class=\"toc-item-num\">1&nbsp;&nbsp;</span>Sync overview</a></span><ul class=\"toc-item\"><li><span><a href=\"#Checking-out-data-from-feature-layers-using-replicas\" data-toc-modified-id=\"Checking-out-data-from-feature-layers-using-replicas-1.1\"><span class=\"toc-item-num\">1.1&nbsp;&nbsp;</span>Checking out data from feature layers using replicas</a></span><ul class=\"toc-item\"><li><span><a href=\"#Verify-if-sync-is-enabled\" data-toc-modified-id=\"Verify-if-sync-is-enabled-1.1.1\"><span class=\"toc-item-num\">1.1.1&nbsp;&nbsp;</span>Verify if sync is enabled</a></span></li><li><span><a href=\"#List-existing-replicas\" data-toc-modified-id=\"List-existing-replicas-1.1.2\"><span class=\"toc-item-num\">1.1.2&nbsp;&nbsp;</span>List existing replicas</a></span></li><li><span><a href=\"#Create-a-replica\" data-toc-modified-id=\"Create-a-replica-1.1.3\"><span class=\"toc-item-num\">1.1.3&nbsp;&nbsp;</span>Create a replica</a></span><ul class=\"toc-item\"><li><span><a href=\"#Verify-Extract-capability\" data-toc-modified-id=\"Verify-Extract-capability-1.1.3.1\"><span class=\"toc-item-num\">1.1.3.1&nbsp;&nbsp;</span>Verify Extract capability</a></span></li></ul></li></ul></li><li><span><a href=\"#Removing-replicas\" data-toc-modified-id=\"Removing-replicas-1.2\"><span class=\"toc-item-num\">1.2&nbsp;&nbsp;</span>Removing replicas</a></span></li></ul></li></ul></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Checking out data from feature layers using replicas\n",
    "To create a replica, we need a feature layer that is sync enabled. We can use the `syncEnabled` property of a `FeatureLayerCollection` object to verify that. If it is compatible, it'll have a `syncCapabilities` property that returns a dictionary with specific sync capabilities.\n",
    "\n",
    "We'll start by establishing a GIS connection."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# connect to a GIS\n",
    "from arcgis.gis import GIS\n",
    "import arcgis.features\n",
    "gis = GIS() # connect to www.arcgis.com anonymously. \n",
    "            # we will use a public sync enabled feature layer"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To create and work with replicas, we need to create a `FeatureLayerCollection` object. A `FeatureLayerCollection` object can be created from either a feature layer `Item` or directly using a feature service URL.\n",
    "\n",
    "Here, we will connect to a sample service by Esri with URL `https://sampleserver6.arcgisonline.com/arcgis/rest/services/Sync/WildfireSync/FeatureServer/`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "url = 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/Sync/WildfireSync/FeatureServer/'\n",
    "wildfire_flc = arcgis.features.FeatureLayerCollection(url, gis)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "arcgis.features.layer.FeatureLayerCollection"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(wildfire_flc)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Verify if sync is enabled\n",
    "\n",
    "Perfect, now we have our `FeatureLayerCollection` object. Accessing the `layers` property on a `FeatureLayerCollection` returns a list of `FeatureLayer` objects. We can create a replica of one of these or all of these layers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<FeatureLayer url:\"https://sampleserver6.arcgisonline.com/arcgis/rest/services/Sync/WildfireSync/FeatureServer//0\">,\n",
       " <FeatureLayer url:\"https://sampleserver6.arcgisonline.com/arcgis/rest/services/Sync/WildfireSync/FeatureServer//1\">,\n",
       " <FeatureLayer url:\"https://sampleserver6.arcgisonline.com/arcgis/rest/services/Sync/WildfireSync/FeatureServer//2\">]"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "wildfire_flc.layers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# query syncEnabled property to verify is sync is enabled\n",
    "wildfire_flc.properties.syncEnabled"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{\n",
       "  \"supportsRegisteringExistingData\": true,\n",
       "  \"supportsSyncDirectionControl\": true,\n",
       "  \"supportsPerLayerSync\": true,\n",
       "  \"supportsPerReplicaSync\": false,\n",
       "  \"supportsRollbackOnFailure\": false,\n",
       "  \"supportsAsync\": true,\n",
       "  \"supportsAttachmentsSyncDirection\": true,\n",
       "  \"supportsSyncModelNone\": true\n",
       "}"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# query the syncCapabilities property to view fine grained capabilities\n",
    "wildfire_flc.properties.syncCapabilities"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### List existing replicas\n",
    "\n",
    "The `replicas` property on a `FeatureLayerCollection` object returns you a `SyncManager` object. This manager object contains the necessary functions for all your sync workflows. You can read more about it [here](https://developers.arcgis.com/python/api-reference/arcgis.features.managers.html#syncmanager).\n",
    "\n",
    "You can determine if any replicas were created earlier on this layer by calling the `get_list()` method on the `SyncManager` object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2515"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "replica_list = wildfire_flc.replicas.get_list()\n",
    "len(replica_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, there are plenty of replicas on this layer. Let's view one:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'replicaName': 'Ags_Fs_1543141555952',\n",
       " 'replicaID': 'A2A63918-F35D-4441-8AE8-693A672F4BED'}"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "replica_list[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create a replica\n",
    "Now, we'll create our own replica of this feature layer. The `create()` method accepts a number of parameters allowing you to adjust what needs to be replicated and customize other options. For more information on this operation, scroll down on the [SyncManager documentation](/python/api-reference/arcgis.features.managers.html#arcgis.features.managers.SyncManager) to the function.\n",
    "\n",
    "The full capability of the sync operation allows you to check out data from a feature layer, make edits, and sync the deltas (changes) back to the server in order to update the features. This is popular in use cases which involve client applications such as ArcGIS Runtime or ArcGIS Desktop applications that check out data, go offline (such as in areas where network connectivity is limited), make edits, then synchronize the data back to the server and update the features. The capability allows multiple clients to do this in parallel, thus enabling a large data collection effort.\n",
    "\n",
    "However, if your purpose of a replica is only to check out the data (one directional), then you can verify if the `extract` capability is enabled on the feature layer and create a replica that is just meant for data check out. We will see this use case below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Create,Delete,Query,Sync,Update,Uploads,Editing'"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# list all capabilities\n",
    "wildfire_flc.properties.capabilities"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We don't see 'Extract', which is what we need. So, let us search for a different layer via a different GIS connection (e.g. use either an existing `profile` or `GIS(url=\"your enterprise\", username='user name', password='password') to set up the connection`)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "portal_gis = GIS(url='https://pythonapi.playground.esri.com/portal', username='arcgis_python', password='amazing_arcgis_123')\n",
    "search_result = portal_gis.content.search(\"Ports in the Western US\", \"Feature Layer\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div class=\"item_container\" style=\"height: auto; overflow: hidden; border: 1px solid #cfcfcf; border-radius: 2px; background: #f6fafa; line-height: 1.21429em; padding: 10px;\">\n",
       "                    <div class=\"item_left\" style=\"width: 210px; float: left;\">\n",
       "                       <a href='https://pythonapi.playground.esri.com/portal/home/item.html?id=6a8766e14d3d41cfb8fc8d4d8974f16d' target='_blank'>\n",
       "                        <img src='' width='200' height='133' class=\"itemThumbnail\">\n",
       "                       </a>\n",
       "                    </div>\n",
       "\n",
       "                    <div class=\"item_right\"     style=\"float: none; width: auto; overflow: hidden;\">\n",
       "                        <a href='https://pythonapi.playground.esri.com/portal/home/item.html?id=6a8766e14d3d41cfb8fc8d4d8974f16d' target='_blank'><b>Ports in the Western US</b>\n",
       "                        </a>\n",
       "                        <br/>Subset of ports data based on US Department of Transportation.<img src='https://pythonapi.playground.esri.com/portal/home/js/jsapi/esri/css/images/item_type_icons/featureshosted16.png' style=\"vertical-align:middle;\">Feature Layer Collection by api_data_owner\n",
       "                        <br/>Last Modified: January 03, 2019\n",
       "                        <br/>0 comments, 61 views\n",
       "                    </div>\n",
       "                </div>\n",
       "                "
      ],
      "text/plain": [
       "<Item title:\"Ports in the Western US\" type:Feature Layer Collection owner:api_data_owner>"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "search_result[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We'll once again create a `FeatureLayerCollection` object from this item"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "arcgis.features.layer.FeatureLayerCollection"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ports_flc = arcgis.features.FeatureLayerCollection.fromitem(search_result[0])\n",
    "type(ports_flc)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Verify Extract capability"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Query,Sync,Extract'"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ports_flc.properties.capabilities"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that we have a suitable feature layer (since `Sync` and `Extract` are both enabled as seen in the capabilities), we can extract the data into a file geodatabase and store it in our local file system. This happens during the replica creation. Let's dive into some more specific details of our replica before creation."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The \"fullExtent\" of the `FeatureLayerCollection` object can be extracted and used to construct the geometry filter we'll use when we call the `create()` method later. When the `geometry_filter` parameter is not specified, the `create()` method will go on to use the source service's full extents to contruct the geometry_filter as default."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'-17764735.369,2241467.173,-13044640.570,5721784.017'"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "extents = ports_flc.properties['fullExtent']\n",
    "extents_str = \",\".join(format(x, \"10.3f\") for x in [extents['xmin'],extents['ymin'],extents['xmax'], extents['ymax']])\n",
    "extents_str"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we are ready to create the geometry filter. `geom_filter` is a spatial filter class object from arcgis.geometry.filters module used to filter results based on a spatial relationship with geometry (in this case, using the full extents of the area of interest, or AOI)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'geometryType': 'esriGeometryEnvelope',\n",
       " 'geometry': '-17764735.369,2241467.173,-13044640.570,5721784.017'}"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "geom_filter = {'geometryType':'esriGeometryEnvelope'}\n",
    "geom_filter.update({'geometry':extents_str})\n",
    "geom_filter"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use the `out_path` to specify the folder path to save the replica file, e.g. executing the cell below would save the replica file at server side location `/arcgis/home/data2403D6ECC5674828945916419D999BCA.geodatabase`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'transportType': 'esriTransportTypeURL',\n",
       " 'replicaName': 'arcgis_python_api_ports',\n",
       " 'responseType': 'esriReplicaResponseTypeData',\n",
       " 'replicaID': '6E679BFE-90AC-453F-AFEC-06610E1EA90D',\n",
       " 'targetType': 'server',\n",
       " 'resultUrl': 'https://pythonapi.playground.esri.com/server/rest/directories/arcgisjobs/system/synctools_gpserver/j8f3cf4b3ee4543f39ac4e9dc22c9db1a/scratch/_ags_data2403D6ECC5674828945916419D999BCA.geodatabase',\n",
       " 'submissionTime': 1554503878000,\n",
       " 'lastUpdatedTime': 1554503880000,\n",
       " 'status': 'Completed'}"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "replica1 = ports_flc.replicas.create( replica_name = 'arcgis_python_api_ports',\n",
    "                                      layers='0',\n",
    "                                      geometry_filter=geom_filter,\n",
    "                                      sync_model=\"perLayer\",\n",
    "                                      target_type=\"server\",\n",
    "                                      data_format=\"sqlite\",\n",
    "                                      out_path=r'/arcgis/home')\n",
    "replica1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There we go! We were able to checkout data from this feature layer into a geodatabase. Clients can use this data in any way they wish, for instance, publish it as another feature layer to a different portal or just store it for archival."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Removing replicas\n",
    "\n",
    "The sync operation is expensive on the resources of your web GIS. Therefore, it is a good maintenance practice to remove unnecessary replicas. An ArcGIS admin could leverage the ArcGIS Python API to script and automate the process of scanning all feature layers and removing stale replicas on each of them.\n",
    "\n",
    "A replica can be removed by calling the `SyncManager`'s `unregister()` method and passing the id of a replica that needs to be removed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let us query all the replicas registered on the ports feature layer from before\n",
    "replica_list = ports_flc.replicas.get_list()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We know from earlier we had a ton of replicas (2515), but what if we only want to unregister the one we just made? Or ones within a certain time window? To dictate that, we can start by looping through each of the replicas returned and use the `get()` method to get detailed information about these replicas and look a `creationDate` property."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Before looping, let us take a deeper look at one of these replicas by calling the `get()` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'replicaName': 'arcgis_python_api_ports',\n",
       " 'replicaID': '6E679BFE-90AC-453F-AFEC-06610E1EA90D',\n",
       " 'replicaOwner': 'arcgis_python',\n",
       " 'spatialReference': {'wkid': 102100, 'latestWkid': 3857},\n",
       " 'layerServerGens': [{'id': 0, 'serverGen': 1554503878473, 'serverSibGen': 0}],\n",
       " 'creationDate': 1554503878473,\n",
       " 'lastSyncDate': 1554503878473,\n",
       " 'syncModel': 'perLayer',\n",
       " 'targetType': 'server',\n",
       " 'syncDirection': 'download',\n",
       " 'returnsAttachments': False,\n",
       " 'returnAttachments': False,\n",
       " 'attachmentsSyncDirection': 'none',\n",
       " 'spatialRel': 'esriSpatialRelIntersects',\n",
       " 'geometry': {'xmin': -17764735.369,\n",
       "  'ymin': 2241467.173,\n",
       "  'xmax': -13044640.57,\n",
       "  'ymax': 5721784.017,\n",
       "  'spatialReference': {'wkid': 102100, 'latestWkid': 3857}},\n",
       " 'layers': [{'id': 0,\n",
       "   'queryOption': 'useFilter',\n",
       "   'useGeometry': True,\n",
       "   'includeRelated': True,\n",
       "   'where': ''}]}"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "replica1 = ports_flc.replicas.get(replica_list[0]['replicaID'])\n",
    "replica1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `creationDate` key is retured as unix epoch time. We need to convert it to local time for processing:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "time.struct_time(tm_year=2019, tm_mon=4, tm_mday=5, tm_hour=15, tm_min=37, tm_sec=58, tm_wday=4, tm_yday=95, tm_isdst=1)"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import time\n",
    "time.localtime(replica1['creationDate']/1000) #dividing by 1000 to convert micro seconds to seconds"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To determine those replicas that were created 10 mins earlier, let us create an epoch timestamp for 10 mins before now and find those replicas whose time stamps are lower than this"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1554504014.728465"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ten_min_earlier_epoch = time.time() - 10\n",
    "ten_min_earlier_epoch"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that we have this value, we can use this and compare all of our replicas' time values. Anything less than this will be older than 10 minutes old; we can use that comparison to determine which ones to delete when we loop through."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'replica_id': '6E679BFE-90AC-453F-AFEC-06610E1EA90D', 'creationDate': 1554503878.473}\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "removal_list = []\n",
    "for r in replica_list:\n",
    "    temp_r = ports_flc.replicas.get(r['replicaID'])\n",
    "    temp_dict = {'replica_id': r['replicaID'],\n",
    "                'creationDate':temp_r['creationDate']/1000}\n",
    "    \n",
    "    #check\n",
    "    if temp_dict['creationDate'] < ten_min_earlier_epoch:\n",
    "        removal_list.append(temp_dict)\n",
    "        print(temp_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let us loop through each of these replicas and remove them using the `unregister()` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'success': True}\n"
     ]
    }
   ],
   "source": [
    "for r in removal_list:\n",
    "    result = ports_flc.replicas.unregister(r['replica_id'])\n",
    "    print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Voila. We've mastered replicas."
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.0"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": true,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": true
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
